﻿#region Apache License Version 2.0
/*----------------------------------------------------------------

Copyright 2024 DJ Zheng & ShangHai GM Nahefa Technology Co.,Ltd.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.

----------------------------------------------------------------*/
#endregion Apache License Version 2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Collector.Device
{
    
    class DeviceCommunicationManager : PulseProtocolPackage, IDeviceCommunication
    {
        private readonly string TAG = "DeviceCommunicationManager:";

        private Dictionary<DeviceEvent, AutoResetEvent> waitEventMap = new Dictionary<DeviceEvent, AutoResetEvent>();

        public DeviceCommunicationManager()
        {
            waitEventMap.Add(DeviceEvent.eDEV_EVENT_RESET, new AutoResetEvent(false));
            waitEventMap.Add(DeviceEvent.eDEV_EVENT_REACHED, new AutoResetEvent(false));
        }

        public bool SendCommandToDevice(in string cmdName)
        {
            byte[] command = FileConfig.getSerialPortData(cmdName);
            Log.Debug(TAG + string.Format("SendCmd -> {0}: {1} , ignore response", cmdName, ConvertFuncs.ByteArrayToHexString(command)));

            DeviceConnector.SendData(command);

            return true;
        }

        public bool SendCommandToDevice(in string cmdKey, byte expectedCode, out byte[] payload, int tryCount = 3)
        {
            bool ret = false;
            string result;

            PulseProtocolPackage.MessageRequest request = new PulseProtocolPackage.MessageRequest();
            request.Command = FileConfig.getSerialPortData(cmdKey);
            Log.Debug(TAG + string.Format("SendCmd -> {0}: {1}", cmdKey, ConvertFuncs.ByteArrayToHexString(request.Command)));

            payload = null;
            for (int i = 0; i < tryCount; i++)
            {
                request.ExpectedCode = expectedCode;
                ret = SendCommand(request, out result);
                Log.Debug(TAG + " ACK <- {0}: {1}", cmdKey, result, LogMsgType.INFO);
                if (ret && request.Payload[IDeviceProtocolShape.FRAME_FIELD_CMD_OFFSET] == expectedCode)
                {
                    int payload_size = request.Payload[IDeviceProtocolShape.FRAME_FIELD_LENGTH_OFFSET] - 1;
                    if (payload_size > 0)
                    {
                        payload = new byte[payload_size];
                        Array.Copy(request.Payload, IDeviceProtocolShape.FRAME_FIELD_PAYLOAD_OFFSET, payload, 0, payload_size);
                    }
                    else
                    {
                        // 保持一致性, 没有payload时填充一个byte 0
                        payload = new byte[] { 0 };
                    }
                    break;
                }
                else
                {
                    ret = false;
                }
            }

            return ret;
        }

        public bool SendCommandToDevice(in byte[] command, in string cmdName, byte expectedCode, out byte[] payload)
        {
            int tryCount = 3;
            bool ret = false;
            string result;

            PulseProtocolPackage.MessageRequest request = new PulseProtocolPackage.MessageRequest();
            request.Command = new byte[command.Length];
            command.CopyTo(request.Command, 0);
            Log.Debug(TAG + string.Format("SendCmd -> {0}: {1}", cmdName, ConvertFuncs.ByteArrayToHexString(request.Command)));

            payload = null;
            for (int i = 0; i < tryCount; i++)
            {
                request.ExpectedCode = expectedCode;
                ret = SendCommand(request, out result);
                Log.Debug(TAG + "ACK <- {0}: {1}", cmdName, result, LogMsgType.INFO);
                if (ret && request.Payload[5] == expectedCode)
                {
                    int payload_size = request.Payload[4] - 1;
                    if (payload_size > 0)
                    {
                        payload = new byte[payload_size];

                        Array.Copy(request.Payload, 6, payload, 0, payload_size);
                    } 
                    else
                    {
                        payload = new byte[] { 0 };
                    }
                    break;
                }
                else
                {
                    ret = false;
                }
            }

            return ret;
        }

        public bool GivebackCompleted(DeviceEvent evt)
        {
            Log.Debug(TAG + "evt code: {0}", evt);
            switch (evt)
            {
                case DeviceEvent.eDEV_EVENT_REACHED:
                    {
                        // 回复设备收到A7事件
                        SendCommandToDevice("SPResponseACK");
                    }
                    break;
                case DeviceEvent.eDEV_EVENT_OVERFLOW:
                    {

                    }
                    break;
                case DeviceEvent.eDEV_EVENT_RESET:
                    {
                        // 回复设备收到A4事件
                        SendCommandToDevice("SPResetACK");
                    }
                    break;
                default:
                    {

                    }
                    break;

            }

            if (waitEventMap.ContainsKey(evt))
            {
                waitEventMap[evt].Set();
            }

            return true;
        }

        public bool ResetDevice()
        {
            return ResetDevice("SPDeviceResetData");
        }

        // 读SN号
        public bool ReadSerialNumber(out string readSN)
        {
            bool ret;
            byte[] payload;

            readSN = "";
            ret = SendCommandToDevice("SPReadPcbSN", 0x24, out payload);
            if (ret)
            {
                // 解析SN

                int myPSN = (payload[3] << 8) | (payload[4]);
                readSN = "GMMX" +
                        ((payload[0] >> 4) & 0x0F).ToString("D1") +
                        (payload[0] & 0x0F).ToString("D2") +
                        payload[1].ToString() +
                        ((payload[2] >> 4) & 0x0F).ToString("D2") +
                        (payload[2] & 0x0F).ToString("D1") +
                        myPSN.ToString("D4");
            }

            return ret;
        }

        public bool WriteSerialNumber(string serialNum, out string reason)
        {
            int offset = 0;
            if (serialNum.Length > 16 + offset)
            {
                reason = "SN号长度错误！";
            }

            if (serialNum.Length != 16 + offset)
            {
                reason = "SN号长度错误！";
            }
            string sn_from_scanner = serialNum.Substring(offset, 16);
            // 检查长度是不是16位，防错
            if (sn_from_scanner.Length != 16)
            {                
                reason = "SN号长度应为16位!";
                return false;
            }

            // 获取相应信息的字符串 GMMX002 2210 2 0001
            string year_str = sn_from_scanner.Substring(7, 2);
            string month_str = sn_from_scanner.Substring(9, 2);
            string prodStatus_str = sn_from_scanner.Substring(11, 1);
            string number_str = sn_from_scanner.Substring(12, 4);
            // 将字符串转化为16进制的数值, 取高八位写入地址
            //out.write((message.length>>8)&0xff);//
            //取低八位写入高地址中
            //out.write(message.length&0xff);
            //取低八位写入高地址中
            int year = Convert.ToInt32(year_str) & 0xFF;
            int month = Convert.ToInt32(month_str) & 0x0F;
            int prodStatus = Convert.ToInt32(prodStatus_str) & 0x0F;
            int number = Convert.ToInt32(number_str) & 0xFFFF;
            Log.Info(TAG + "number & 0xFF :" + (number & 0xFF) + ",number >> 8 & 0xFF :" + (number >> 8 & 0xFF));
            byte[] byteCmd = {Convert.ToByte(number & 0xFF), Convert.ToByte(number >> 8 & 0xFF),
                    Convert.ToByte(month << 4 | prodStatus), Convert.ToByte(year) };
            byte[] byteCmd_ = FileConfig.getSerialPortData("SPWritePcbSN");
            byte[] Cmdata_set_sn = new byte[byteCmd.Length + byteCmd_.Length];
            byteCmd_.CopyTo(Cmdata_set_sn, 0);
            byteCmd.CopyTo(Cmdata_set_sn, byteCmd_.Length);

            // Cmdata分别表示为帧头（4个字节），长度，指令，年份，月份(高四位)|序号高4位，序号9-16位，序号低8位
            //byte[] Cmdata_set_sn = { 0x55, 0x55, 0xaa, 0xaa, 0x05, 0xc2, Convert.ToByte(number & 0xFF), Convert.ToByte(number >> 8 & 0xFF),
            //Convert.ToByte(month << 4 | number >> 16), Convert.ToByte(year) };

            Log.Info(TAG + "Write SN(0:X2) :" + ConvertFuncs.ByteArrayToHexString(Cmdata_set_sn), LogMsgType.INFO);

            byte[] payload;
            bool ret = SendCommandToDevice(Cmdata_set_sn, "SPWritePcbSN", 0xC4, out payload);
            if(!ret)
            {
                reason = "write sn err";
            }
            
            reason = "Write SN OK";

            return ret;
        }

        // 设置左右手，只针对三部，寸和尺镜像
        public bool SetHandDirection(bool isRight)
        {
            // 打开脉象设备灯，保持长亮
            string[] cmdName = new string[] { "SPSetLeftHandDirection", "SPSetRightHandDirection" };
            byte[] payload;
            int index = (isRight) ? 1 : 0;            
            bool ret = SendCommandToDevice(cmdName[index], 0x60, out payload);
            if(ret && payload[0] != 0)
            {
                ret = false;
            }
            return ret;
        }

        // 读初始静压值
        public bool ReadInitialPPressure(ref double baseline)
        {
            bool ret;
            byte[] payload;

            ret = SendCommandToDevice("SPInitialPulsePressure", 0x91, out payload);
            if (ret)
            {
                baseline = ((payload[1] & 0x0F) << 16 | (payload[2] << 8) | (payload[3]));
                baseline /= 100;
            }

            return ret;
        }

        public bool ReadInitialPPressure(ref double[] baseline)
        {
            bool ret;
            byte[] payload;

            ret = SendCommandToDevice("SPInitialPulsePressure", 0x91, out payload);
            if (ret)
            {
                // Only one sensor
                if (payload.Length == 0x04)
                {
                    baseline[0] = ((payload[1] & 0x0F) << 16 | (payload[2] << 8) | (payload[3]));
                    baseline[0] /= 100;
                } 
                // There are three sensor
                else if(payload.Length == 0x09)
                {
                    baseline[0] = ((payload[0] & 0x0F) << 16 | (payload[1] << 8) | (payload[2]));
                    baseline[0] /= 100;
                    baseline[1] = ((payload[3] & 0x0F) << 16 | (payload[4] << 8) | (payload[5]));
                    baseline[1] /= 100;
                    baseline[2] = ((payload[6] & 0x0F) << 16 | (payload[7] << 8) | (payload[8]));
                    baseline[2] /= 100;
                }
            }

            return ret;
        }

        // 读设备版本号
        public bool ReadMcuVersion(out string mcuVersion)
        {
            bool ret;
            byte[] version;
            mcuVersion = "";

            ret = SendCommandToDevice("SPReadMcuVersion", 0x44, out version);
            if (ret)
            {
                Log.Debug("ReadMcuVersion, payload :" + ConvertFuncs.ByteArrayToHexString(version), LogMsgType.INFO);
                mcuVersion = System.Text.Encoding.ASCII.GetString(version).TrimEnd('\0');
            }


            if (ret && string.IsNullOrWhiteSpace(mcuVersion))
                return false;

            return ret;
        }

        // 读气压零漂值
        public bool ReadAirPressureOfPeeling(out double pressure)
        {
            bool ret;            
            byte[] ReadResult;

            ret = SendCommandToDevice("ReadInitAir", 0x98, out ReadResult);
            if (ret)
            {
                pressure = ((ReadResult[1]) | (ReadResult[0] << 8));
            }
            else
            {
                pressure = 0;
            }

            return ret;
        }

        // 零压校准
        public bool AirpressureCalibration()
        {
            bool ret;
            byte[] payload;

            ret = SendCommandToDevice("ResetInitAir", 0x97, out payload);
            if (ret && payload[0] == 0x00)
            {
                return true;
            }

            return false;
        }

        // 写设备固定频率
        public bool WriteDeviceFixedFre(string fixedFreInfo)
        {
            //new byte[] { 0x55, 0x55, 0xaa, 0xaa, 0x01, 0x42 };
            byte[] fixedFreCmd = FileConfig.getSerialPortData("SPWriteFixedFreq");
            byte[] fixedFreq_byte = Encoding.ASCII.GetBytes(fixedFreInfo);
            fixedFreCmd[4] = ConvertFuncs.intToByte(fixedFreq_byte.Length + 1);//字串长度
            byte[] final_cmd_mmHg = new byte[fixedFreCmd.Length + fixedFreq_byte.Length];
            fixedFreCmd.CopyTo(final_cmd_mmHg, 0);
            fixedFreq_byte.CopyTo(final_cmd_mmHg, fixedFreCmd.Length);

            byte[] payload;
            SendCommandToDevice(final_cmd_mmHg, "SPWriteFixedFreq", 0x99, out payload);
            if (payload != null && payload[0] == 0x00)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // 读设备固定频率
        public bool ReadDeviceFixedFre(out string fixedFrequency)
        {
            bool ret;
            byte[] payload;

            fixedFrequency = "";
            ret = SendCommandToDevice("SPReadFixedFreq", 0x9A, out payload);
            if (ret)
            {
                // 发现固定频率
                if (payload.Length > 1)
                {
                    Log.Debug("ReadDeviceFixedFre, freq :" + ConvertFuncs.ByteArrayToHexString(payload), LogMsgType.INFO);
                    fixedFrequency = System.Text.Encoding.ASCII.GetString(payload).TrimEnd('\0');

                }
                else // 没有固定频率 
                {
                    Log.Debug("DeviceFixedFre is not write", LogMsgType.ERROR);
                    ret = false;
                }
            }

            return ret;
        }

        // 打开LED
        public bool OpenDeviceLed()
        {
            // 打开脉象设备灯，保持长亮
            string cmdName = "SPLedON";
            byte[] cmdata = FileConfig.getSerialPortData(cmdName);
            byte[] payload;
            //ret = mPulseProtocolPkgProcesser.SendCommand(cmdata, 0x54, instanceSerPort, out result);
            return SendCommandToDevice(cmdata, cmdName, 0x54, out payload);
        }

        // 设置LED闪烁
        public bool SPLedFlicker()
        {
            // 打开脉象设备灯，保持长亮
            string cmdName = "SPLedFlicker";
            byte[] cmdata = FileConfig.getSerialPortData(cmdName);
            byte[] payload;
            //ret = mPulseProtocolPkgProcesser.SendCommand(cmdata, 0x54, instanceSerPort, out result);
            return SendCommandToDevice(cmdata, cmdName, 0x54, out payload);
        }

        // SPDeviceResetData
        public bool ResetDevice(string key)
        {
            bool ret;
            byte[] command = FileConfig.getSerialPortData(key);            
            byte[] payload;

            ret = SendCommandToDevice(command, key, 0x92, out payload);
            if(ret)
            {
                if(payload[0] == (byte)DeviceRespondCode.eDEV_ACK_CODE_OK || payload[0] == (byte)DeviceRespondCode.eDEV_ACK_CODE_IS_RESETING)
                {
                    // Host should to wait reset finished. wait time about 5s.
                    if (waitEventMap.ContainsKey(DeviceEvent.eDEV_EVENT_RESET))
                    {
                        ret = waitEventMap[DeviceEvent.eDEV_EVENT_RESET].WaitOne(8 * 1000);
                    }

                    Log.Debug(TAG + "ResetDevice is {0}", ret);
                }
                else if(payload[0] != (byte)DeviceRespondCode.eDEV_ACK_CODE_ALREADY_RESET) 
                {
                    ret = false;
                    Log.Debug(TAG + "ResetDevice is false", LogMsgType.ERROR);
                }
            } 

            return ret;
        }
    }
}
